---
title: "Record"
description: "Record types in ReScript"
canonical: "/docs/manual/record"
section: "Language Features"
order: 6
---

# Record

Records are like JavaScript objects but:

- are immutable by default
- have fixed fields (not extensible)

## Type Declaration

A record needs a mandatory type declaration:

<CodeTab labels={["ReScript", "JS Output"]}>

```res prelude
type person = {
  age: int,
  name: string,
}
```

```js
// Empty output
```

</CodeTab>

You can also nest definitions of records.

<CodeTab labels={["ReScript", "JS Output"]}>

```res example
type person = {
  age: int,
  name: string,
  notificationSettings: {
    sendEmails: bool,
    allowPasswordLogin: bool,
  },
}

let person = {
  age: 90,
  name: "Test Person",
  notificationSettings: {
    sendEmails: true,
    allowPasswordLogin: false,
  },
}

```

```js
let person = {
  age: 90,
  name: "Test Person",
  notificationSettings: {
    sendEmails: true,
    allowPasswordLogin: false,
  },
};
```

</CodeTab>

Nesting record definitions is a nice way to group records that are part of the same structure, and won't be referenced from the outside.

If you end up needing to refer to a nested record type explicitly, you should make it an explicit definition instead of a nested one. This is mainly for 2 reasons:

- The records that are automatically generated for the nested record definitions are named in a way that would require you to use escaped identifiers to reference them. The nested record at `notificationSettings` above would be named `\"person.notificationSettings"` for instance
- For the sake of clarity (and caring about your co-workers), having an explicit and named definition to look at and refer to is much easier than scanning a potentially large record definition for the nested record you're looking for

So if we in the example above ended up needing to refer to `person.notificationSettings` nested record from the outside, we should instead make it explicit, just like how we normally define records:

<CodeTab labels={["ReScript", "JS Output"]}>

```res example
type personNotificationSettings = {
  sendEmails: bool,
  allowPasswordLogin: bool,
}

type person = {
  age: int,
  name: string,
  notificationSettings: personNotificationSettings
}

let person = {
  age: 90,
  name: "Test Person",
  notificationSettings: {
    sendEmails: true,
    allowPasswordLogin: false,
  },
}

```

```js
let person = {
  age: 90,
  name: "Test Person",
  notificationSettings: {
    sendEmails: true,
    allowPasswordLogin: false,
  },
};
```

</CodeTab>

## Creation

To create a `person` record (declared above):

<CodeTab labels={["ReScript", "JS Output"]}>

```res prelude
let me = {
  age: 5,
  name: "Big ReScript"
}
```

```js
var me = {
  age: 5,
  name: "Big ReScript",
};
```

</CodeTab>

When you create a new record value, ReScript tries to find a record type declaration that conforms to the shape of the value. So the `me` value here is inferred as of type `person`.

The type is found by looking above the `me` value. **Note**: if the type instead resides in another file or module, you need to explicitly indicate which file or module it is:

<CodeTab labels={["ReScript", "JS Output"]}>

```res example
// School.res
type person = {age: int, name: string}
```

```js
// Empty output
```

</CodeTab>

<CodeTab labels={["ReScript", "JS Output"]}>

```res
// Example.res

let me: School.person = {age: 20, name: "Big ReScript"}
/* or */
let me2 = {School.age: 20, name: "Big ReScript"}
```

```js
var me = {
  age: 20,
  name: "Big ReScript",
};
var me2 = {
  age: 20,
  name: "Big ReScript",
};
```

</CodeTab>

In both `me` and `me2` the record definition from `School` is found. The first one, `me` with the regular type annotation, is preferred.

## Access

Use the familiar dot notation:

<CodeTab labels={["ReScript", "JS Output"]}>

```res example
let name = me.name
```

```js
var name = "Big ReScript";
```

</CodeTab>

## Immutable Update

New records can be created from old records with the `...` spread operator. The original record isn't mutated.

<CodeTab labels={["ReScript", "JS Output"]}>

```res example
let meNextYear = {...me, age: me.age + 1}
```

```js
var meNextYear = {
  age: 21,
  name: "Big ReScript",
};
```

</CodeTab>

**Note**: spread cannot add new fields to the record value, as a record's shape is fixed by its type.

## Mutable Update

Record fields can optionally be mutable. This allows you to efficiently update those fields in-place with the `=` operator.

<CodeTab labels={["ReScript", "JS Output"]}>

```res example
type person = {
  name: string,
  mutable age: int
}

let baby = {name: "Baby ReScript", age: 5}
baby.age = baby.age + 1 // `baby.age` is now 6. Happy birthday!
```

```js
var baby = {
  name: "Baby ReScript",
  age: 5,
};

baby.age = (baby.age + 1) | 0;
```

</CodeTab>

Fields not marked with `mutable` in the type declaration cannot be mutated.

## JavaScript Output

ReScript records compile to straightforward JavaScript objects; see the various JS output tabs above.

## Optional Record Fields

ReScript [`v10`](../../blog/release-10-0-0.mdx#experimental-optional-record-fields) introduced optional record fields. This means that you can define fields that can be omitted when creating the record. It looks like this:

<CodeTab labels={["ReScript", "JS Output"]}>

```res example
type person = {
  age: int,
  name?: string
}
```

```js
// Empty output
```

</CodeTab>

Notice how `name` has a suffixed `?`. That means that the field itself is _optional_.

### Creation

You can omit any optional fields when creating a record. Not setting an optional field will default the field's value to `None`:

<CodeTab labels={["ReScript", "JS Output"]}>

```res example
type person = {
  age: int,
  name?: string
}

let me = {
  age: 5,
  name: "Big ReScript"
}

let friend = {
  age: 7
}
```

```js
var me = {
  age: 5,
  name: "Big ReScript",
};

var friend = {
  age: 7,
};
```

</CodeTab>

This has consequences for pattern matching, which we'll expand a bit on soon.

## Immutable Update

Updating an optional field via an immutable update above lets you set that field value without needing to care whether it's optional or not.

<CodeTab labels={["ReScript", "JS Output"]}>

```res example
type person = {
  age: int,
  name?: string
}

let me = {
  age: 123,
  name: "Hello"
}

let withoutName = {
  ...me,
  name: "New Name"
}
```

```js
import * as Caml_obj from "./stdlib/caml_obj.js";

var me = {
  age: 123,
  name: "Hello",
};

var newrecord = Caml_obj.obj_dup(me);

newrecord.name = "New Name";

var withoutName = newrecord;
```

</CodeTab>

However, if you want to set the field to an optional value, you prefix that value with `?`:

<CodeTab labels={["ReScript", "JS Output"]}>

```res example
type person = {
  age: int,
  name?: string
}

let me = {
  age: 123,
  name: "Hello"
}

let maybeName = Some("My Name")

let withoutName = {
  ...me,
  name: ?maybeName
}
```

```js
import * as Caml_obj from "./stdlib/caml_obj.js";

var me = {
  age: 123,
  name: "Hello",
};

var maybeName = "My Name";

var newrecord = Caml_obj.obj_dup(me);

newrecord.name = maybeName;

var withoutName = newrecord;
```

</CodeTab>

You can unset an optional field's value via that same mechanism by setting it to `?None`.

### Pattern Matching on Optional Fields

[Pattern matching](./pattern-matching-destructuring.mdx), one of ReScript's most important features, has two caveats when you deal with optional fields.

When matching on the value directly, it's an `option`. Example:

<CodeTab labels={["ReScript", "JS Output"]}>

```res
type person = {
  age: int,
  name?: string,
}

let me = {
  age: 123,
  name: "Hello",
}

let isRescript = switch me.name {
| Some("ReScript") => true
| Some(_) | None => false
}
```

```js
var isRescript;

isRescript = "Hello" === "ReScript" ? true : false;

var me = {
  age: 123,
  name: "Hello",
};
```

</CodeTab>

But, when matching on the field as part of the general record structure, it's treated as the underlying, non-optional value:

<CodeTab labels={["ReScript", "JS Output"]}>

```res
type person = {
  age: int,
  name?: string,
}

let me = {
  age: 123,
  name: "Hello",
}

let isRescript = switch me {
| {name: "ReScript"} => true
| _ => false
}

```

```js
var isRescript;

isRescript = "Hello" === "ReScript" ? true : false;

var me = {
  age: 123,
  name: "Hello",
};
```

</CodeTab>

Sometimes you _do_ want to know whether the field was set or not. You can tell the pattern matching engine about that by prefixing your option match with `?`, like this:

<CodeTab labels={["ReScript", "JS Output"]}>

```res
type person = {
  age: int,
  name?: string,
}

let me = {
  age: 123,
  name: "Hello",
}

let nameWasSet = switch me {
| {name: ?None} => false
| {name: ?Some(_)} => true
}
```

```js
var nameWasSet = true;

var me = {
  age: 123,
  name: "Hello",
};
```

</CodeTab>

## Record Type Spread

In ReScript v11, you can now spread one or more record types into a new record type. It looks like this:

```rescript
type a = {
  id: string,
  name: string,
}

type b = {
  age: int
}

type c = {
  ...a,
  ...b,
  active: bool
}
```

`type c` will now be:

```rescript
type c = {
  id: string,
  name: string,
  age: int,
  active: bool,
}
```

Record type spreads act as a 'copy-paste' mechanism for fields from one or more records into a new record. This operation inlines the fields from the spread records directly into the new record definition, while preserving their original properties, such as whether they are optional or mandatory. It's important to note that duplicate field names are not allowed across the records being spread, even if the fields have the same type.

## Record Type Coercion

Record type coercion gives us more flexibility when passing around records in our application code. In other words, we can now coerce a record `a` to be treated as a record `b` at the type level, as long as the original record `a` contains the same set of fields in `b`. Here's an example:

```rescript
type a = {
  name: string,
  age: int,
}

type b = {
  name: string,
  age: int,
}

let nameFromB = (b: b) => b.name

let a: a = {
  name: "Name",
  age: 35,
}

let name = nameFromB(a :> b)
```

Notice how we _coerced_ the value `a` to type `b` using the coercion operator `:>`. This works because they have the same record fields. This is purely at the type level, and does not involve any runtime operations.

Additionally, we can also coerce records from `a` to `b` whenever `a` is a super-set of `b` (i.e. `a` containing all the fields of `b`, and more). The same example as above, slightly altered:

```rescript
type a = {
  id: string,
  name: string,
  age: int,
  active: bool,
}

type b = {
  name: string,
  age: int,
}

let nameFromB = (b: b) => b.name

let a: a = {
  id: "1",
  name: "Name",
  age: 35,
  active: true,
}

let name = nameFromB(a :> b)
```

Notice how `a` now has more fields than `b`, but we can still coerce `a` to `b` because `b` has a subset of the fields of `a`.

In combination with [optional record fields](./record.mdx#optional-record-fields), one may coerce a mandatory field of an `option` type to an optional field:

```rescript
type a = {
  name: string,

  // mandatory, but explicitly typed as option<int>
  age: option<int>,
}

type b = {
  name: string,
  // optional field
  age?: int,
}

let nameFromB = (b: b) => b.name

let a: a = {
  name: "Name",
  age: Some(35),
}

let name = nameFromB(a :> b)
```

## Tips & Tricks

### Record Types Are Found By Field Name

With records, you **cannot** say "I'd like this function to take any record type, as long as they have the field `age`". The following **won't work as intended**:

<CodeTab labels={["ReScript", "JS Output"]}>

```res
type person = {age: int, name: string}
type monster = {age: int, hasTentacles: bool}

let getAge = (entity) => entity.age
```

```js
function getAge(entity) {
  return entity.age;
}
```

</CodeTab>

Instead, `getAge` will infer that the parameter `entity` must be of type `monster`, the closest record type with the field `age`. The following code's last line fails:

```res
let kraken = {age: 9999, hasTentacles: true}
let me = {age: 5, name: "Baby ReScript"}

getAge(kraken)
getAge(me) // type error!
```

The type system will complain that `me` is a `person`, and that `getAge` only works on `monster`. If you need such capability, use ReScript objects, described [here](./object.mdx).

### Optional Fields in Records Can Be Useful for Bindings

Many JavaScript APIs tend to have large configuration objects that can be a bit annoying to model as records, since you previously always needed to specify all record fields when creating a record.

Optional record fields, introduced in [`v10`](../../blog/release-10-0-0.mdx#experimental-optional-record-fields), is intended to help with this. Optional fields will let you avoid having to specify all fields, and let you just specify the one's you care about. A significant improvement in ergonomics for bindings and other APIs with for example large configuration objects.

## Design Decisions

After reading the constraints in the previous sections, and if you're coming from a dynamic language background, you might be wondering why one would bother with record in the first place instead of straight using object, since the former needs explicit typing and doesn't allow different records with the same field name to be passed to the same function, etc.

1. The truth is that most of the times in your app, your data's shape is actually fixed, and if it's not, it can potentially be better represented as a combination of variant (introduced next) + record instead.

2. Since a record type is resolved through finding that single explicit type declaration (we call this "nominal typing"), the type error messages end up better than the counterpart ("structural typing", like for tuples). This makes refactoring easier; changing a record type's fields naturally allows the compiler to know that it's still the same record, just misused in some places. Otherwise, under structural typing, it might get hard to tell whether the definition site or the usage site is wrong.
